// in order to get function prototypes from glext.h, define GL_GLEXT_PROTOTYPES before including glext.h
#define GL_GLEXT_PROTOTYPES

#include <MGLib.h>
#include <iostream>
#include <sstream>
#include <string>
#include <iomanip>
#include <cstdlib>
#include "GBuffer.h"
#include "LightDirectional.h"
#include "LightPoint.h"

#define		PAUSEON(arg)	if(arg)getchar();

#define		NUM_SHADERS		(3)
#define		NUM_TEXTURES	(3)

using std::stringstream;
using std::string;
using std::cout;
using std::endl;
using std::ends;

GBuffer		bufferGeo;
GBuffer		bufferLight;

GLuint		shaderCount;
CgProgram	cgProgramInit[NUM_SHADERS];
CgProgram	cgProgramFinal;
CgProgram	cgProgramLights[2];

DirectionalLight	g_dirLight;
std::vector<PointLight>		g_pointLight;
unsigned int				g_cPL;

Model		model_betty;
Model		model_sarah;

Texture		tex_betty;
Texture		tex_sarah;

int			final_shader_opt = 5;

// GLUT CALLBACK functions ////////////////////////////////////////////////////
void displayCB();
void reshapeCB(int w, int h);
void timerCB(int millisec);
void idleCB();
void keyboardCB(unsigned char key, int x, int y);
void mouseCB(int button, int stat, int x, int y);
void mouseMotionCB(int x, int y);

// function declearations /////////////////////////////////////////////////////
void initGL();
void initPrograms();
int  initGLUT(int argc, char **argv);
bool initSharedMem();
void clearSharedMem();
void initLights();
void setCamera(float posX, float posY, float posZ, float targetX, float targetY, float targetZ);
void drawString(const char *str, int x, int y, float color[4], void *font);
void drawString3D(const char *str, float pos[3], float color[4], void *font);
void showInfo();
void showFPS();

void drawToFBO();
void drawScreen();
void drawCube();
void drawLights();

// constants
const int SCREEN_WIDTH = 900;
const int SCREEN_HEIGHT = 900;
const int TEXTURE_WIDTH = 2048;
const int TEXTURE_HEIGHT = 2048;

// global variables
void *font = GLUT_BITMAP_8_BY_13;
bool mouseLeftDown;
bool mouseRightDown;
float mouseX, mouseY;
float cameraAngleX;
float cameraAngleY;
float cameraDistance;
int screenWidth, screenHeight;
bool fboSupported;
int drawMode = 0;
Timer timer, t1;
float playTime;                     // to compute rotation angle
float renderToTextureTime;          // elapsed time for render-to-texture


void drawToFBO()
{
    // adjust viewport and projection matrix to texture dimension
    glViewport(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT);

	glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60.0f, (float)(screenWidth)/screenHeight, 1.0f, 100.0f);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

	glClearColor(1, 1, 1, 1);

	for( GLuint i = 0; i < shaderCount; i++ )
	{
		bufferGeo.begin();
		
		cgProgramInit[i].Use();

		glPushMatrix();

		glTranslatef(0, 0, cameraDistance);
		glRotatef(cameraAngleX, 1, 0, 0);   // pitch
		glRotatef(cameraAngleY, 0, 1, 0);   // heading

		glPushMatrix();
		cgProgramInit[i].SetParameterStateMatrix( "ModelViewProj", CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );
		cgProgramInit[i].SetParameterStateMatrix( "MVIT", CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE_TRANSPOSE );
		tex_betty.bind( GL_TEXTURE0 );
		glColor3f(1.0f,1.0f,1.0f);
		model_betty.Draw();
		glPopMatrix();

		glPushMatrix();
		glTranslatef( 1.5f, 0, 0 );
		cgProgramInit[i].SetParameterStateMatrix( "ModelViewProj", CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );
		cgProgramInit[i].SetParameterStateMatrix( "MVIT", CG_GL_MODELVIEW_MATRIX, CG_GL_MATRIX_INVERSE_TRANSPOSE );
		tex_sarah.bind( GL_TEXTURE0 );
		glColor3f(1.0f,1.0f,1.0f);
		model_sarah.Draw();
		glPopMatrix();

		if(final_shader_opt!=5)
		{
			for(unsigned int n=0;n<g_pointLight.size();n++)
				g_pointLight[n].RenderDebug(&cgProgramInit[i]);
		}

		glPopMatrix();

		cgProgramInit[i].Use(false);

		bufferGeo.end( i );
	}
}


void drawLights()
{
	glClearColor(0, 0, 0, 0);

//	glDisable(GL_CULL_FACE);
	glDisable(GL_DEPTH_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE,GL_ONE);

	bufferLight.begin();

	glPushMatrix();

	glTranslatef(0, 0, cameraDistance);
	glRotatef(cameraAngleX, 1, 0, 0);   // pitch
	glRotatef(cameraAngleY, 0, 1, 0);   // heading

	if( g_dirLight.GetPower() > 0.01f )
	{
		glPushMatrix();
		g_dirLight.Update();
		g_dirLight.Render();
		glPopMatrix();
	}
	
	for(unsigned int n=0;n<g_pointLight.size();n++)
	{
		if( g_pointLight[n].GetRange() > 0.01f )
		{
			glPushMatrix();
			g_pointLight[n].Update();
			g_pointLight[n].Render();
			glPopMatrix();
		}
	}
	
	glPopMatrix();

	bufferLight.end();

	glDisable(GL_BLEND);
	glEnable(GL_CULL_FACE);
	glEnable(GL_DEPTH_TEST);
}


void drawScreen()
{
	glViewport(0, 0, screenWidth, screenHeight);

    // set to 2D orthogonal projection
    glMatrixMode(GL_PROJECTION);        // switch to projection matrix
    glLoadIdentity();                   // reset projection matrix

    glMatrixMode(GL_MODELVIEW);      // switch to modelview matrix
    glLoadIdentity();                   // reset modelview matrix

	glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	cgProgramFinal.Use();
	cgProgramFinal.SetParameterStateMatrix( "ModelViewProj", CG_GL_MODELVIEW_PROJECTION_MATRIX, CG_GL_MATRIX_IDENTITY );
	cgProgramFinal.SetParameterStateMatrix( "Proj", CG_GL_PROJECTION_MATRIX, CG_GL_MATRIX_INVERSE );
	cgProgramFinal.SetParameter1i("opt", final_shader_opt);

	glBegin(GL_QUADS);
		glTexCoord2f(0, 0); glVertex3f(-1.0f,-1.0f, .5f);
		glTexCoord2f(1, 0); glVertex3f( 1.0f,-1.0f, .5f);
		glTexCoord2f(1, 1); glVertex3f( 1.0f, 1.0f, .5f);
		glTexCoord2f(0, 1); glVertex3f(-1.0f, 1.0f, .5f);
	glEnd();

	cgProgramFinal.Use(false);

    // restore modelview matrix
    glMatrixMode(GL_MODELVIEW);      // switch to modelview matrix
}

///////////////////////////////////////////////////////////////////////////////
int main(int argc, char **argv)
{
    initSharedMem();

    // register exit callback
    //atexit(exitCB);

    // init GLUT and GL
    initGLUT(argc, argv);
    initPrograms();
    initGL();

    // get OpenGL info
    glInfo glInfo;
    glInfo.getInfo();
    glInfo.printSelf();

    if(glInfo.isExtensionSupported("GL_EXT_framebuffer_object"))
    {
        fboSupported = true;
        cout << "Video card supports GL_EXT_framebuffer_object." << endl;
    }
    else
    {
        fboSupported = false;
        cout << "Video card does NOT support GL_EXT_framebuffer_object." << endl;
    }

    if(fboSupported)
    {
		bufferGeo.init(TEXTURE_WIDTH,TEXTURE_HEIGHT,NUM_TEXTURES,true,false);
		bufferGeo.create();
		//bufferGeo.useFBO(false);

		bufferLight.init(TEXTURE_WIDTH,TEXTURE_HEIGHT,1,false,false);
		bufferLight.create();
    }

	model_betty.loadOBJ( "betty/betty.obj" );
	model_sarah.loadOBJ( "sarah/sarah.obj" );

	tex_betty.loadTGA( "betty/betty.tga", true, GL_RGB );
	tex_sarah.loadTGA( "sarah/sarah.tga", true, GL_RGB );

    // start timer, the elapsed time will be used for rotating the teapot
    timer.start();

    // the last GLUT call (LOOP)
    // window will be shown and display callback is triggered by events
    // NOTE: this call never return main().
    glutMainLoop(); /* Start GLUT event-processing loop */

    return 0;
}


///////////////////////////////////////////////////////////////////////////////
// initialize GLUT for windowing
///////////////////////////////////////////////////////////////////////////////
int initGLUT(int argc, char **argv)
{
    // GLUT stuff for windowing
    // initialization openGL window.
    // It must be called before any other GLUT routine.
    glutInit(&argc, argv);

    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_ALPHA);   // display mode

    glutInitWindowSize(screenWidth, screenHeight);              // window size

    glutInitWindowPosition(100, 100);                           // window location

    // finally, create a window with openGL context
    // Window will not displayed until glutMainLoop() is called
    // It returns a unique ID.
    int handle = glutCreateWindow(argv[0]);     // param is the title of window

	ASSERT( glewInit() == GLEW_OK );

    // register GLUT callback functions
    glutDisplayFunc(displayCB);
    //glutTimerFunc(33, timerCB, 33);             // redraw only every given millisec
    glutIdleFunc(idleCB);                       // redraw whenever system is idle
    glutReshapeFunc(reshapeCB);
    glutKeyboardFunc(keyboardCB);
    glutMouseFunc(mouseCB);
    glutMotionFunc(mouseMotionCB);

    return handle;
}

///////////////////////////////////////////////////////////////////////////////
// initialize shaders
///////////////////////////////////////////////////////////////////////////////
void initPrograms()
{
    int colorBufferCount = 0;
    glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS_EXT, &colorBufferCount);

	LocalFile shaderFile("initial.cg");
	if( colorBufferCount >= NUM_SHADERS )
	{
		shaderCount = 1;
		//const char* compilerArguments[] = {"-po", "ARB_draw_buffers", 0};
		PAUSEON( !cgProgramInit[0].Load(&shaderFile, &shaderFile, "vertex", "fragment"/*, NULL, compilerArguments*/) );
		PAUSEON( !cgProgramInit[0].Compile() );
		PAUSEON( !cgProgramInit[0].Link() );
		cgProgramInit[0].LoadParameters();
	}
	else
	{
		shaderCount = NUM_SHADERS;
		stringstream ssfrag;
		for( int i = 0; i < NUM_SHADERS; i++ )
		{
			ssfrag.str(string(""));
			ssfrag << "fragment" << i;
			PAUSEON( !cgProgramInit[i].Load(&shaderFile, &shaderFile, "vertex", ssfrag.str().c_str()/*, NULL, compilerArguments*/) );
			PAUSEON( !cgProgramInit[i].Compile() );
			PAUSEON( !cgProgramInit[i].Link() );
			cgProgramInit[i].LoadParameters();
		}
	}
	
	shaderFile.Open("final.cg");
	PAUSEON( !cgProgramFinal.Load(&shaderFile, &shaderFile, "vertex", "fragment") );
	PAUSEON( !cgProgramFinal.Compile() );
	PAUSEON( !cgProgramFinal.Link() );
	cgProgramFinal.LoadParameters();
	
	shaderFile.Open("light.cg");

	PAUSEON( !cgProgramLights[0].Load(&shaderFile, &shaderFile, "vertex_dir", "fragment_dir") );
	PAUSEON( !cgProgramLights[0].Compile() );
	PAUSEON( !cgProgramLights[0].Link() );
	cgProgramLights[0].LoadParameters();

	PAUSEON( !cgProgramLights[1].Load(&shaderFile, &shaderFile, "vertex_point", "fragment_point") );
	PAUSEON( !cgProgramLights[1].Compile() );
	PAUSEON( !cgProgramLights[1].Link() );
	cgProgramLights[1].LoadParameters();
}

///////////////////////////////////////////////////////////////////////////////
// initialize OpenGL
// disable unused features
///////////////////////////////////////////////////////////////////////////////
void initGL()
{
    //glShadeModel(GL_SMOOTH);                    // shading mathod: GL_SMOOTH or GL_FLAT
    glPixelStorei(GL_UNPACK_ALIGNMENT, 4);      // 4-byte pixel alignment

    // enable /disable features
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    //glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    //glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
    glEnable(GL_DEPTH_TEST);
    //glEnable(GL_LIGHTING);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_CULL_FACE);

     // track material ambient and diffuse from surface color, call it before glEnable(GL_COLOR_MATERIAL)
    //glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
    //glEnable(GL_COLOR_MATERIAL);

    glClearColor(0, 0, 0, 0);                   // background color
    glClearStencil(0);                          // clear stencil buffer
    glClearDepth(1.0f);                         // 0 is near, 1 is far
    glDepthFunc(GL_LEQUAL);

    //initLights();
    //setCamera(0, 0, 6, 0, 0, 0);

	g_dirLight.SetShader( &cgProgramLights[0] );
	g_dirLight.SetColor( 1.0f, 1.0f, 1.0f, 1.0f );
	g_dirLight.SetDirection( 1, -1, -1 );
	g_dirLight.SetPower( 0.3f );

	g_cPL=0;
	g_pointLight.push_back(PointLight());
	g_pointLight[0].SetShader( &cgProgramLights[1] );
	g_pointLight[0].SetColor( 0.7f, 0.7f, 1.0f, 1.0f );
	g_pointLight[0].SetPosition( 0.75f, 1.0f, 0.0f );
	g_pointLight[0].SetRange( 2.0f );
}



///////////////////////////////////////////////////////////////////////////////
// write 2d text using GLUT
// The projection matrix must be set to orthogonal before call this function.
///////////////////////////////////////////////////////////////////////////////
void drawString(const char *str, int x, int y, float color[4], void *font)
{
    glPushAttrib(GL_LIGHTING_BIT | GL_CURRENT_BIT); // lighting and color mask
    glDisable(GL_LIGHTING);     // need to disable lighting for proper text color
    glDisable(GL_TEXTURE_2D);

    glColor4fv(color);          // set text color
    glRasterPos2i(x, y);        // place text position

    // loop all characters in the string
    while(*str)
    {
        glutBitmapCharacter(font, *str);
        ++str;
    }

    glEnable(GL_TEXTURE_2D);
    glEnable(GL_LIGHTING);
    glPopAttrib();
}



///////////////////////////////////////////////////////////////////////////////
// draw a string in 3D space
///////////////////////////////////////////////////////////////////////////////
void drawString3D(const char *str, float pos[3], float color[4], void *font)
{
    glPushAttrib(GL_LIGHTING_BIT | GL_CURRENT_BIT); // lighting and color mask
    glDisable(GL_LIGHTING);     // need to disable lighting for proper text color

    glColor4fv(color);          // set text color
    glRasterPos3fv(pos);        // place text position

    // loop all characters in the string
    while(*str)
    {
        glutBitmapCharacter(font, *str);
        ++str;
    }

    glEnable(GL_LIGHTING);
    glPopAttrib();
}



///////////////////////////////////////////////////////////////////////////////
// initialize global variables
///////////////////////////////////////////////////////////////////////////////
bool initSharedMem()
{
    screenWidth = SCREEN_WIDTH;
    screenHeight = SCREEN_HEIGHT;
    mouseLeftDown = mouseRightDown = false;
    cameraAngleX = cameraAngleY = 45;
    cameraDistance = -10;
    playTime = 0;
    return true;
}


///////////////////////////////////////////////////////////////////////////////
// initialize lights
///////////////////////////////////////////////////////////////////////////////
void initLights()
{
    // set up light colors (ambient, diffuse, specular)
    GLfloat lightKa[] = {.2f, .2f, .2f, 1.0f};  // ambient light
    GLfloat lightKd[] = {.7f, .7f, .7f, 1.0f};  // diffuse light
    GLfloat lightKs[] = {1, 1, 1, 1};           // specular light
    glLightfv(GL_LIGHT0, GL_AMBIENT, lightKa);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, lightKd);
    glLightfv(GL_LIGHT0, GL_SPECULAR, lightKs);

    // position the light
    float lightPos[4] = {0, 0, 20, 1}; // positional light
    glLightfv(GL_LIGHT0, GL_POSITION, lightPos);

    glEnable(GL_LIGHT0);                        // MUST enable each light source after configuration
}


///////////////////////////////////////////////////////////////////////////////
// set camera position and lookat direction
///////////////////////////////////////////////////////////////////////////////
void setCamera(float posX, float posY, float posZ, float targetX, float targetY, float targetZ)
{
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(posX, posY, posZ, targetX, targetY, targetZ, 0, 1, 0); // eye(x,y,z), focal(x,y,z), up(x,y,z)
}


///////////////////////////////////////////////////////////////////////////////
// display info messages
///////////////////////////////////////////////////////////////////////////////
void showInfo()
{
    // backup current model-view matrix
    glPushMatrix();                     // save current modelview matrix
    glLoadIdentity();                   // reset modelview matrix

    // set to 2D orthogonal projection
    glMatrixMode(GL_PROJECTION);        // switch to projection matrix
    glPushMatrix();                     // save current projection matrix
    glLoadIdentity();                   // reset projection matrix
    gluOrtho2D(0, screenWidth, 0, screenHeight);  // set to orthogonal projection

    const int FONT_HEIGHT = 14;
    float color[4] = {1, 0, 0, 1};

    stringstream ss;
    
	ss << "FBO: ";
	if(bufferGeo.isUsingFBO())
        ss << "on" << ends;
    else
        ss << "off" << ends;

    drawString(ss.str().c_str(), 1, screenHeight-FONT_HEIGHT, color, font);
    ss.str(""); // clear buffer

	ss << std::fixed << std::setprecision(3);
    ss << "Render-To-Texture Time: " << renderToTextureTime << " ms" << ends;
    drawString(ss.str().c_str(), 1, screenHeight-(2*FONT_HEIGHT), color, font);
    ss.str("");

    // unset floating format
    ss << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield);

    // restore projection matrix
    glPopMatrix();                   // restore to previous projection matrix

    // restore modelview matrix
    glMatrixMode(GL_MODELVIEW);      // switch to modelview matrix
    glPopMatrix();                   // restore to previous modelview matrix
}



///////////////////////////////////////////////////////////////////////////////
// display frame rates
///////////////////////////////////////////////////////////////////////////////
void showFPS()
{
    static Timer timer;
    static int count = 0;
    static stringstream ss;
    double elapsedTime;

    // backup current model-view matrix
    glPushMatrix();                     // save current modelview matrix
    glLoadIdentity();                   // reset modelview matrix

    // set to 2D orthogonal projection
    glMatrixMode(GL_PROJECTION);        // switch to projection matrix
    glPushMatrix();                     // save current projection matrix
    glLoadIdentity();                   // reset projection matrix
    gluOrtho2D(0, screenWidth, 0, screenHeight); // set to orthogonal projection

    float color[4] = {0, 0, 1, 1};

    // update fps every second
    elapsedTime = timer.getElapsedTime();
    if(elapsedTime < 1.0)
    {
        ++count;
    }
    else
    {
        ss.str("");
        ss << std::fixed << std::setprecision(1);
        ss << (count / elapsedTime) << " FPS" << ends; // update fps string
        ss << std::resetiosflags(std::ios_base::fixed | std::ios_base::floatfield);
        count = 0;                      // reset counter
        timer.start();                  // restart timer
    }
    drawString(ss.str().c_str(), screenWidth-85, screenHeight-14, color, font);

    // restore projection matrix
    glPopMatrix();                      // restore to previous projection matrix

    // restore modelview matrix
    glMatrixMode(GL_MODELVIEW);         // switch to modelview matrix
    glPopMatrix();                      // restore to previous modelview matrix
}

//=============================================================================
// CALLBACKS
//=============================================================================

void displayCB()
{
    // render to texture //////////////////////////////////////////////////////
    t1.start();

	drawToFBO();

	// measure the elapsed time of render-to-texture
    t1.stop();
    renderToTextureTime = t1.getElapsedTimeInMilliSec();
    ///////////////////////////////////////////////////////////////////////////

	bufferGeo.generateMipMaps();

	bufferGeo.bindDepthTexture(GL_TEXTURE0);
	bufferGeo.bindTexture(GL_TEXTURE1,0);
	bufferGeo.bindTexture(GL_TEXTURE2,1);
	bufferGeo.bindTexture(GL_TEXTURE3,2);

	drawLights();

	bufferLight.generateMipMaps();

	bufferGeo.bindDepthTexture(GL_TEXTURE0);
	bufferGeo.bindTexture(GL_TEXTURE1,0);
	bufferGeo.bindTexture(GL_TEXTURE2,1);
	bufferGeo.bindTexture(GL_TEXTURE3,2);
	bufferLight.bindTexture(GL_TEXTURE4,0);

	//drawCube();
	drawScreen();

    // draw info messages
    showInfo();
    showFPS();

    glutSwapBuffers();
}


void reshapeCB(int width, int height)
{
    screenWidth = width;
    screenHeight = height;
}


void timerCB(int millisec)
{
    glutTimerFunc(millisec, timerCB, millisec);
    glutPostRedisplay();
}


void idleCB()
{
    glutPostRedisplay();
}


void keyboardCB(unsigned char key, int x, int y)
{
	char *pTexBuffer = NULL;
	float m;
    
	switch(key)
    {
    case 27: // ESCAPE
        exit(0);
        break;

	case '`':
		g_pointLight.push_back(PointLight());
		g_pointLight.back().SetRange(1.5f);
		g_pointLight.back().SetPosition( 0.75f, 1.0f, 0.0f );
		g_pointLight.back().SetShader( &cgProgramLights[1] );
		g_pointLight.back().SetColor( float(rand()) / RAND_MAX, float(rand()) / RAND_MAX, float(rand()) / RAND_MAX, 1.0f );
		m = max( g_pointLight.back().GetColor()[0], max( g_pointLight.back().GetColor()[1], g_pointLight.back().GetColor()[2] ) ) + 0.01f;
		g_pointLight.back().GetColor()[0] /= m;
		g_pointLight.back().GetColor()[1] /= m;
		g_pointLight.back().GetColor()[2] /= m;
		DEBUG_OUT("[PLight]++ =%d\n", g_pointLight.size());
		break;
	case '\\':
		g_pointLight[g_cPL].SetColor( float(rand()) / RAND_MAX, float(rand()) / RAND_MAX, float(rand()) / RAND_MAX, 1.0f );
		m = max( g_pointLight[g_cPL].GetColor()[0], max( g_pointLight[g_cPL].GetColor()[1], g_pointLight[g_cPL].GetColor()[2] ) ) + 0.01f;
		g_pointLight[g_cPL].GetColor()[0] /= m;
		g_pointLight[g_cPL].GetColor()[1] /= m;
		g_pointLight[g_cPL].GetColor()[2] /= m;
		DEBUG_OUT("[PLight] #=%d random color (%.2f,%.2f,%.2f)\n", g_cPL,g_pointLight[g_cPL].GetColor()[0], g_pointLight[g_cPL].GetColor()[1], g_pointLight[g_cPL].GetColor()[2] );
		break;
	case '~':
		g_pointLight.pop_back();
		DEBUG_OUT("[PLight]-- =%d\n", g_pointLight.size());
		while(g_cPL>=g_pointLight.size())g_cPL--;
		DEBUG_OUT("[PLight] #=%d\n", g_cPL);
		break;
	case '\t':
		g_cPL++;
		g_cPL %= g_pointLight.size();
		DEBUG_OUT("[PLight] #=%d\n", g_cPL);
		break;
	case '2':
		g_pointLight[g_cPL].SetRange(g_pointLight[g_cPL].GetRange() + 0.05f);
		DEBUG_OUT("[Point] range=%.2f\n", g_pointLight[g_cPL].GetRange());
		break;
	case '1':
		g_pointLight[g_cPL].SetRange(g_pointLight[g_cPL].GetRange() - 0.05f);
		DEBUG_OUT("[Point] range=%.2f\n", g_pointLight[g_cPL].GetRange());
		break;
#define PRINTPOS DEBUG_OUT("[Point] pos=(%.2f,%.2f,%.2f)\n", g_pointLight[g_cPL].GetPosition()[0], g_pointLight[g_cPL].GetPosition()[1], g_pointLight[g_cPL].GetPosition()[2]);
	case 'x':
		g_pointLight[g_cPL].GetPosition()[0] += 0.1f;
		PRINTPOS;
		break;
	case 'z':
		g_pointLight[g_cPL].GetPosition()[0] -= 0.1f;
		PRINTPOS;
		break;
	case 'q':
		g_pointLight[g_cPL].GetPosition()[1] += 0.1f;
		PRINTPOS;
		break;
	case 'a':
		g_pointLight[g_cPL].GetPosition()[1] -= 0.1f;
		PRINTPOS;
		break;
	case 'w':
		g_pointLight[g_cPL].GetPosition()[2] += 0.1f;
		PRINTPOS;
		break;
	case 's':
		g_pointLight[g_cPL].GetPosition()[2] -= 0.1f;
		PRINTPOS;
		break;

	case '4':
		g_dirLight.SetPower(g_dirLight.GetPower() + 0.01f);
		DEBUG_OUT("[Dir] power=%.2f\n", g_dirLight.GetPower());
		break;
	case '3':
		g_dirLight.SetPower(g_dirLight.GetPower() - 0.01f);
		DEBUG_OUT("[Dir] power=%.2f\n", g_dirLight.GetPower());
		break;

    case ' ':
		pTexBuffer = (char*) malloc( sizeof(char) * TEXTURE_WIDTH * TEXTURE_HEIGHT * 3 );
		bufferGeo.bindTexture(GL_TEXTURE0,0);
		glGetTexImage( GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, pTexBuffer );
		if( pTexBuffer )
		{
			saveTGAToFile( "img.tga", TEXTURE_WIDTH, TEXTURE_HEIGHT, pTexBuffer );
		}
		free(pTexBuffer);
		break;

	case '=':
		final_shader_opt++;
		if( final_shader_opt > 5 )
		{
			final_shader_opt = 0;
		}
		DEBUG_OUT("[Final] opt=%d\n", final_shader_opt);
		break;
	case '-':
		final_shader_opt--;
		if( final_shader_opt < 0 )
		{
			final_shader_opt = 5;
		}
		DEBUG_OUT("[Final] opt=%d\n", final_shader_opt);
		break;

    case 'd': // switch rendering modes (fill -> wire -> point)
    case 'D':
        drawMode = ++drawMode % 3;
        if(drawMode == 0)        // fill mode
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
            glEnable(GL_DEPTH_TEST);
            glEnable(GL_CULL_FACE);
        }
        else if(drawMode == 1)  // wireframe mode
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_CULL_FACE);
        }
        else                    // point mode
        {
            glPolygonMode(GL_FRONT_AND_BACK, GL_POINT);
            glDisable(GL_DEPTH_TEST);
            glDisable(GL_CULL_FACE);
        }
        break;

    default:
        ;
    }
}


void mouseCB(int button, int state, int x, int y)
{
    mouseX = x;
    mouseY = y;

    if(button == GLUT_LEFT_BUTTON)
    {
        if(state == GLUT_DOWN)
        {
            mouseLeftDown = true;
        }
        else if(state == GLUT_UP)
            mouseLeftDown = false;
    }

    else if(button == GLUT_RIGHT_BUTTON)
    {
        if(state == GLUT_DOWN)
        {
            mouseRightDown = true;
        }
        else if(state == GLUT_UP)
            mouseRightDown = false;
    }
}


void mouseMotionCB(int x, int y)
{
    if(mouseLeftDown)
    {
        cameraAngleY += (x - mouseX);
        cameraAngleX += (y - mouseY);
        mouseX = x;
        mouseY = y;
    }
    if(mouseRightDown)
    {
        cameraDistance += (y - mouseY) * 0.2f;
        mouseY = y;
    }
}
